package org.hackystat.ui.projectviewer.gwt.client.controller;

import org.hackystat.ui.projectviewer.gwt.client.ProjectViewer;
import org.hackystat.ui.projectviewer.gwt.client.dataprovider.DataProviderMediator;
import org.hackystat.ui.projectviewer.gwt.client.panels.ProjectDataPanel;
import org.hackystat.ui.projectviewer.gwt.client.widgets.InvitationActionsWidget;
import org.hackystat.ui.projectviewer.gwt.client.widgets.InvitationListWidget;
import org.hackystat.ui.projectviewer.gwt.client.widgets.ProjectActionsWidget;
import org.hackystat.ui.projectviewer.gwt.client.widgets.ProjectListWidget;

import com.allen_sauer.gwt.log.client.Log;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.ChangeListener;
import com.google.gwt.user.client.ui.ClickListener;
import com.google.gwt.user.client.ui.ClickListenerCollection;
import com.google.gwt.user.client.ui.DialogBox;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.SourcesClickEvents;
import com.google.gwt.user.client.ui.Widget;

/**
 * Controller for the actions.
 *
 * @author Pavel Senin.
 *
 */
public class ProjectViewerController implements ClickListener, ChangeListener {

  /** The singleton instance of the mediator. */
  private static ProjectViewerController instance;

  private final DeleteProjectYesNoDialog deleteProjectDialog = new DeleteProjectYesNoDialog();

  // this two variables are used to keep track of latest action and project name for YesNoDialog
  private String projectSelectionMem = null;
  private String actionSelectionMem = null;

  /**
   * Constructor.
   */
  private ProjectViewerController() {
    super();
    deleteProjectDialog.addClickListener(this);
    deleteProjectDialog.hide();
  }

  /**
   * Reports the singletone instance.
   *
   * @return The instance.
   */
  public static ProjectViewerController getInstance() {
    if (null == instance) {
      instance = new ProjectViewerController();
    }
    return instance;
  }

  /**
   * {@inheritDoc}
   */
  public void onClick(Widget sender) {
    if (DataProviderMediator.getInstance().isLocked()) {
      // exiting from here since provider is locked - work is ongoing and we want it to finish
      if (Log.isDebugEnabled()) {
        Log.debug("PVC: provider is locked! exiting...");
      }
      assert true;
    }
    else {
      // checking if we getting click from the ProjectList widget
      if (sender instanceof ProjectListWidget) {
        if (Log.isDebugEnabled()) {
          Log.debug("PVC: Got click from ProjectListWidget, processing ... ");
        }
        processProjectListWidget((ProjectListWidget) sender);
      }
      else if (sender instanceof InvitationListWidget) {
        if (Log.isDebugEnabled()) {
          Log.debug("PVC: Got click from InvitationListWidget, processing ... ");
        }
        processInvitationsListWidget((InvitationListWidget) sender);
      }
      else if (sender instanceof ProjectDataPanel) {
        if (Log.isDebugEnabled()) {
          Log.debug("PVC: Got click from ProjectDataPanel, processing ... ");
        }
        processDataDownloadRequest((ProjectDataPanel) sender);
      }
      else if (sender instanceof DeleteProjectYesNoDialog) {
        if (Log.isDebugEnabled()) {
          Log.debug("PVC: Got click from DeleteProjectYesNoDialog, processing ... ");
        }
        // first of all hide the dialog
        deleteProjectDialog.hide();
        // now check if there was some selection - proceed with action or just cancel
        if (("OK".equalsIgnoreCase(deleteProjectDialog.getAction()))
            && (null != this.projectSelectionMem) && (null != this.actionSelectionMem)) {
          ProjectViewer.get().deleteProject(this.projectSelectionMem);
        }
        else {
          ProjectViewer.get().resetButtons();
          ProjectViewer.get().setSelection(this.projectSelectionMem);
        }
      }
    }
  }

  /**
   * Processes request for downloading CSV-formatted data from the server.
   *
   * @param sender The ProjectDataPanel instance firing action.
   */
  private void processDataDownloadRequest(ProjectDataPanel sender) {
    DataProviderMediator.getInstance().getSDTData(sender.getProjectSelection(),
        sender.getSDTSelection());
  }

  /**
   * Processes "click" event from the InvitationListWidget.
   *
   * @param invitListWidget The InvitationListWidget instance which sources clicks.
   */
  private void processInvitationsListWidget(InvitationListWidget invitListWidget) {
    // checking if we getting click from the InvitationsList widget
    // if so - extract action and selected project
    String projectSelection = ((InvitationListWidget) invitListWidget).getSelection();
    String projectAction = ((InvitationListWidget) invitListWidget).getAction();
    // if we just wondering about - display info
    if (InvitationActionsWidget.ACTION_VIEW.equalsIgnoreCase(projectAction)) {
      ProjectViewer.get().setSelection(projectSelection);
    } // if we want to accept invitation - do it
    else if (InvitationActionsWidget.ACTION_ACCEPT.equalsIgnoreCase(projectAction)) {
      GWT.log("Accepting invitation for " + projectSelection, null);
      AsyncCallback<Boolean> callback = new AsyncCallback<Boolean>() {
        // If the call was successful, we will get back the result of authorization.
        public void onSuccess(Boolean accepted) {
          DataProviderMediator.getInstance().unlock();
          if (accepted) {
            ProjectViewer.get().refresh();
          }
          else {
            // --><><>< lock RPC and display some clues about why we unable to accept invitation
            if (DataProviderMediator.getInstance().lock()) {
              DataProviderMediator.getInstance().getException(new AsyncCallback<String>() {
                public void onSuccess(String result) {
                  DataProviderMediator.getInstance().unlock();
                  Window.alert("Unable to accept invitation, please provide feedback "
                      + "to hackystat-dev@googlegroups.com. \n Error: " + result);
                }

                public void onFailure(Throwable ex) {
                  DataProviderMediator.getInstance().unlock();
                  Window.alert("Unknown exception happend during RPC call. \n Please provide "
                      + "feedback to hackystat-dev@googlegroups.com. \n Error: " + ex.getMessage());
                }
              });
            } // --><><>< end of error retrieving code.
          }
        }

        // Something went wrong with the call. Handle the issue how you'd like.
        public void onFailure(Throwable ex) {
          DataProviderMediator.getInstance().unlock();
          Window.alert("Unknown exception happend during RPC call. \n Please provide "
              + "feedback to hackystat-dev@googlegroups.com. \n Error: " + ex.getMessage());
        }
      };
      if (DataProviderMediator.getInstance().lock()) {
        DataProviderMediator.getInstance().acceptInvitation(projectSelection, callback);
      }
      else {
        Window.alert("RPC is locked, unable to proceed with communications. \n Please provide "
            + "feedback to hackystat-dev@googlegroups.com.");
      }
    }
    else if (InvitationActionsWidget.ACTION_DECLINE.equalsIgnoreCase(projectAction)) {
      // if we want to create new one - do it
      GWT.log("Accepting invitation for " + projectSelection, null);
      AsyncCallback<Boolean> callback = new AsyncCallback<Boolean>() {
        // If the call was successful, we will get back the result of authorization.
        public void onSuccess(Boolean declined) {
          DataProviderMediator.getInstance().unlock();
          if (declined) {
            ProjectViewer.get().refresh();
          }
          else {
            // --><><>< lock RPC and display some clues about why we unable to decline
            // invitation
            if (DataProviderMediator.getInstance().lock()) {
              DataProviderMediator.getInstance().getException(new AsyncCallback<String>() {
                public void onSuccess(String result) {
                  DataProviderMediator.getInstance().unlock();
                  Window.alert("Unable to decline invitation, please provide feedback "
                      + "to hackystat-dev@googlegroups.com. \n Error: " + result);
                }

                public void onFailure(Throwable ex) {
                  DataProviderMediator.getInstance().unlock();
                  Window.alert("Unknown exception happend during RPC call. \n Please provide "
                      + "feedback to hackystat-dev@googlegroups.com. \n Error: " + ex.getMessage());
                }
              });
            } // --><><>< end of error retrieving code.
          }
        }

        // Something went wrong with the call. Handle the issue how you'd like.
        public void onFailure(Throwable ex) {
          DataProviderMediator.getInstance().unlock();
          Window.alert("Unknown exception happend during RPC call. \n Please provide "
              + "feedback to hackystat-dev@googlegroups.com. \n Error: " + ex.getMessage());
        }
      };
      if (DataProviderMediator.getInstance().lock()) {
        DataProviderMediator.getInstance().declineInvitation(projectSelection, callback);
      }
      else {
        Window.alert("RPC is locked, unable to proceed with communications. \n Please provide "
            + "feedback to hackystat-dev@googlegroups.com.");
      }
    }
  }

  /**
   * Processes "click" event from ProjectListWidget.
   *
   * @param projectListWidget The ProjectListWidget pointer.
   */
  private void processProjectListWidget(ProjectListWidget projectListWidget) {
    // if so - extract action and selected project
    String projectSelection = projectListWidget.getSelection();
    String projectAction = projectListWidget.getAction();
    if ((null == projectSelection)
        && (!ProjectActionsWidget.ACTION_NEW.equalsIgnoreCase(projectAction))) {
      ProjectViewer.get().refresh();
      Window.alert("Please make a project selection before firing an action.");
    }
    else {
      // if we just wondering about - display info
      if (ProjectActionsWidget.ACTION_VIEW.equalsIgnoreCase(projectAction)) {
        GWT.log("firing action view on project " + projectSelection, null);
        ProjectViewer.get().resetButtons();
        ProjectViewer.get().setSelection(projectSelection);
      }
      else if (ProjectActionsWidget.ACTION_EDIT.equalsIgnoreCase(projectAction)) {
        // if we want to edit - do it
        if ("Default".equalsIgnoreCase(projectSelection)) {
          Window.alert("Unable to change \"Default\" project, sorry");
          ProjectViewer.get().resetButtons();
          ProjectViewer.get().setSelection(projectSelection);
        }
        else {
          ProjectViewer.get().editProject(projectSelection, false);
        }
      }
      else if (ProjectActionsWidget.ACTION_GETDATA.equalsIgnoreCase(projectAction)) {
        ProjectViewer.get().startDataRetrieval(projectSelection);
      }
      else if (ProjectActionsWidget.ACTION_CHART.equalsIgnoreCase(projectAction)) {
        ProjectViewer.get().startChartRendering(projectSelection);
      }
      else if (ProjectActionsWidget.ACTION_DIARY.equalsIgnoreCase(projectAction)) {
        ProjectViewer.get().startProjectDiary(projectSelection);
      }
      else if (ProjectActionsWidget.ACTION_NEW.equalsIgnoreCase(projectAction)) {
        // if we want to create new one - do it
        ProjectViewer.get().editProject(null, true);
      }
      else if (ProjectActionsWidget.ACTION_DELETE.equalsIgnoreCase(projectAction)) {
        // if we want to create new one - do it
        if ("Default".equalsIgnoreCase(projectSelection)) {
          Window.alert("Unable to delete \"Default\" project, sorry");
          ProjectViewer.get().resetButtons();
          ProjectViewer.get().setSelection(projectSelection);
        }
        else {
          // memorize action and project
          this.projectSelectionMem = projectSelection;
          this.actionSelectionMem = ProjectActionsWidget.ACTION_DELETE;
          deleteProjectDialog.setProjectName(projectSelection);
          deleteProjectDialog.center();
          deleteProjectDialog.show();
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  public void onChange(Widget sender) {
    assert true;
  }

  /**
   * A simple dialog box that displays a message, a Frame, and a close button.
   *
   * @author Pavel Senin.
   */
  private static class DeleteProjectYesNoDialog extends DialogBox implements ClickListener,
      SourcesClickEvents {

    private final ClickListenerCollection clickListeners = new ClickListenerCollection();

    final HTML msg = new HTML("", true);
    final Button okButton = new Button("OK");
    final Button cancelButton = new Button("Cancel");
    final HorizontalPanel buttons = new HorizontalPanel();
    private String action;

    /**
     * Constructor.
     */
    public DeleteProjectYesNoDialog() {
      this.setText("Please confirm:");
      okButton.addClickListener(this);
      buttons.add(okButton);
      cancelButton.addClickListener(this);
      buttons.add(cancelButton);

      DockPanel dock = new DockPanel();
      dock.setSpacing(4);

      dock.add(buttons, DockPanel.SOUTH);
      dock.add(msg, DockPanel.NORTH);

      dock.setCellHorizontalAlignment(buttons, DockPanel.ALIGN_CENTER);
      dock.setWidth("100%");
      setWidget(dock);
    }

    /**
     * Sets the right HTML text.
     *
     * @param projectSelection The project name.
     */
    public void setProjectName(String projectSelection) {
      msg.setHTML("<p> Delete project <b>" + projectSelection + "</b></p>");
    }

    /**
     * {@inheritDoc}
     */
    public void onClick(Widget sender) {
      if (sender == okButton) {
        if (Log.isDebugEnabled()) {
          Log.debug("YesNoDlg: OK clicked ");
        }
        action = "OK";
      }
      else {
        if (Log.isDebugEnabled()) {
          Log.debug("YesNoDlg: CANCEL clicked ");
        }
        action = "CANCEL";
      }
      clickListeners.fireClick(this);
    }

    /**
     * Reports action.
     *
     * @return The action.
     */
    public String getAction() {
      return this.action;
    }

    /**
     * {@inheritDoc}
     */
    public void addClickListener(ClickListener listener) {
      this.clickListeners.add(listener);
    }

    /**
     * {@inheritDoc}
     */
    public void removeClickListener(ClickListener listener) {
      this.clickListeners.remove(listener);
    }

  }

  /**
   * Resets short-term memory. Need this for double check of project deletion.
   */
  public void resetMemory() {
    // null all memories :)
    this.actionSelectionMem = null;
    this.projectSelectionMem = null;
  }

}
